<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Spring源码系列-第8章-SpringMVC子容器和Spring父容器的启动原理 | 风祈的时光录</title><meta name="keywords" content="Spring，框架，spring源码"><meta name="author" content="youthlql"><meta name="copyright" content="youthlql"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="SpringMVC子容器和Spring父容器的启动原理">
<meta property="og:type" content="article">
<meta property="og:title" content="Spring源码系列-第8章-SpringMVC子容器和Spring父容器的启动原理">
<meta property="og:url" content="https://imlql.cn/post/c8dd1418.html">
<meta property="og:site_name" content="风祈的时光录">
<meta property="og:description" content="SpringMVC子容器和Spring父容器的启动原理">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://npm.elemecdn.com/lql_static@latest/logo/spring.png">
<meta property="article:published_time" content="2022-06-20T04:01:02.000Z">
<meta property="article:modified_time" content="2022-07-24T11:22:15.034Z">
<meta property="article:author" content="youthlql">
<meta property="article:tag" content="Spring源码">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://npm.elemecdn.com/lql_static@latest/logo/spring.png"><link rel="shortcut icon" href="https://npm.elemecdn.com/youthlql@1.0.8/blog/favicon.png"><link rel="canonical" href="https://imlql.cn/post/c8dd1418"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//hm.baidu.com"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://npm.elemecdn.com/@fortawesome/fontawesome-free@6/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://npm.elemecdn.com/@fancyapps/ui/dist/fancybox.css" media="print" onload="this.media='all'"><script>var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?f693ff99cc7e613b88cf5b729a14b48b";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Spring源码系列-第8章-SpringMVC子容器和Spring父容器的启动原理',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-07-24 19:22:15'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="https://npm.elemecdn.com/lql_static@latest/butterfly_static/css/ali_icon.css"><link rel="stylesheet" href="https://npm.elemecdn.com/lql_static@latest/butterfly_static/css/2-24-mogai.css"><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="风祈的时光录" type="application/atom+xml">
</head><body><div id="web_bg"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/avatar/2.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data is-center"><div class="data-item"><a href="/archives/"><div class="headline">文章</div><div class="length-num">37</div></a></div><div class="data-item"><a href="/tags/"><div class="headline">标签</div><div class="length-num">21</div></a></div><div class="data-item"><a href="/categories/"><div class="headline">分类</div><div class="length-num">13</div></a></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw iconfont icon-shouyex"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw iconfont icon-zuixinwenzhang_huaban"></i><span> 找文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw iconfont icon-fenlei1"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw iconfont icon-biaoqian1"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw iconfont icon-shijianzhou"></i><span> 归档</span></a></li></ul></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://npm.elemecdn.com/lql_static@latest/logo/spring.png')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">风祈的时光录</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw iconfont icon-shouyex"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw iconfont icon-zuixinwenzhang_huaban"></i><span> 找文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw iconfont icon-fenlei1"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw iconfont icon-biaoqian1"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw iconfont icon-shijianzhou"></i><span> 归档</span></a></li></ul></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">Spring源码系列-第8章-SpringMVC子容器和Spring父容器的启动原理</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-06-20T04:01:02.000Z" title="发表于 2022-06-20 12:01:02">2022-06-20</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-07-24T11:22:15.034Z" title="更新于 2022-07-24 19:22:15">2022-07-24</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Spring/">Spring</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Spring/%E6%BA%90%E7%A0%81V1/">源码V1</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">5.4k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>24分钟</span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h1 id="第8章-SpringMVC子容器和Spring父容器的启动原理"><a href="#第8章-SpringMVC子容器和Spring父容器的启动原理" class="headerlink" title="第8章-SpringMVC子容器和Spring父容器的启动原理"></a>第8章-SpringMVC子容器和Spring父容器的启动原理</h1><h2 id="测试类"><a href="#测试类" class="headerlink" title="测试类"></a>测试类</h2><p>根据<a target="_blank" rel="noopener" href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#spring-web">官方文档</a>写咱们的测试类</p>
<h3 id="AppStarter"><a href="#AppStarter" class="headerlink" title="AppStarter"></a>AppStarter</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.imlql.web;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.imlql.web.config.AppConfig;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.WebApplicationInitializer;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.context.support.AnnotationConfigWebApplicationContext;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.servlet.DispatcherServlet;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.ServletContext;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.ServletException;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.ServletRegistration;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 只要写了这个，相当于配置了SpringMVC的DispatcherServlet</span></span><br><span class="line"><span class="comment"> * 1、Tomcat一启动就加载他</span></span><br><span class="line"><span class="comment"> *        1）、创建了容器、制定了配置类（所有ioc、aop等spring的功能就ok）</span></span><br><span class="line"><span class="comment"> *        2）、注册一个Servlet；    DispatcherServlet；</span></span><br><span class="line"><span class="comment"> *        3）、以后所有的请求都交给了 DispatcherServlet；</span></span><br><span class="line"><span class="comment"> *     效果，访问Tomcat部署的这个Web应用下的所有请求都会被     DispatcherServlet 处理</span></span><br><span class="line"><span class="comment"> *     DispatcherServlet就会进入强大的基于注解的mvc处理流程（<span class="doctag">@GetMapping</span>）</span></span><br><span class="line"><span class="comment"> * 必须Servlet3.0以上才可以；Tomcat6.0以上才支持Servlet3.0规范</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Servlet3.0是javaEE的Web的规范标准，Tomcat是Servlet3.0规范的一个实现；</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AppStarter</span> <span class="keyword">implements</span> <span class="title">WebApplicationInitializer</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStartup</span><span class="params">(ServletContext servletContext)</span> <span class="keyword">throws</span> ServletException </span>&#123; <span class="comment">//Spring会给我们传入servletContext</span></span><br><span class="line">		<span class="comment">//创建ioc容器</span></span><br><span class="line">		AnnotationConfigWebApplicationContext context = <span class="keyword">new</span> AnnotationConfigWebApplicationContext();</span><br><span class="line">		context.register(AppConfig.class); <span class="comment">//传入一个配置类</span></span><br><span class="line">		<span class="comment">//以上截止，ioc容器都没有启动</span></span><br><span class="line">		<span class="comment">//自己newDispatcherServlet，并传入容器</span></span><br><span class="line">		DispatcherServlet servlet = <span class="keyword">new</span> DispatcherServlet(context);</span><br><span class="line">		<span class="comment">//利用Servlet规范</span></span><br><span class="line">		ServletRegistration.Dynamic registration = servletContext.addServlet(<span class="string">&quot;app&quot;</span>, servlet);</span><br><span class="line">		registration.setLoadOnStartup(<span class="number">1</span>);</span><br><span class="line">		registration.addMapping(<span class="string">&quot;/&quot;</span>); <span class="comment">//映射路径，写/就是所有的请求都交给DispatcherServlet</span></span><br><span class="line"></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="HelloController"><a href="#HelloController" class="headerlink" title="HelloController"></a>HelloController</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   <span class="meta">@GetMapping(&quot;/hello&quot;)</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> String <span class="title">sayHello</span><span class="params">()</span></span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;Hello, SpringMVC!&quot;</span>;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="AppConfig"><a href="#AppConfig" class="headerlink" title="AppConfig"></a>AppConfig</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ComponentScan(&quot;cn.imlql.web&quot;)</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AppConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013185625476.png"  />

<p>根路径是在这里配的，tomcat的配置自己百度下，很简单</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013185659518.png"  />

<h2 id="Java的SPI机制"><a href="#Java的SPI机制" class="headerlink" title="Java的SPI机制"></a>Java的SPI机制</h2><blockquote>
<p>下面会用到SPI，这里先讲一下</p>
</blockquote>
<h3 id="项目目录"><a href="#项目目录" class="headerlink" title="项目目录"></a>项目目录</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">spi-demo</span><br><span class="line">├── api-db-impl-mysql/</span><br><span class="line">|  ├── api-db-impl-mysql.iml</span><br><span class="line">|  ├── pom.xml</span><br><span class="line">|  ├── src/</span><br><span class="line">|  |  ├── main/</span><br><span class="line">|  |  |  ├── java/</span><br><span class="line">|  |  |  |  └── com/</span><br><span class="line">|  |  |  |     └── imlql/</span><br><span class="line">|  |  |  |        └── mysql/</span><br><span class="line">|  |  |  |           └── MySQLSaveService.java</span><br><span class="line">|  |  |  └── resources/</span><br><span class="line">|  |  |     └── META-INF/</span><br><span class="line">|  |  |        └── services/</span><br><span class="line">|  |  |           └── cn.imlql.data.DataSaveService</span><br><span class="line">|  |  └── test/</span><br><span class="line">|  |     └── java/</span><br><span class="line">|  └── target/</span><br><span class="line">|     ├── classes/</span><br><span class="line">|     |  ├── com/</span><br><span class="line">|     |  |  └── imlql/</span><br><span class="line">|     |  |     └── mysql/</span><br><span class="line">|     |  |        └── MySQLSaveService.class</span><br><span class="line">|     |  └── META-INF/</span><br><span class="line">|     |     └── services/</span><br><span class="line">|     |        └── cn.imlql.data.DataSaveService</span><br><span class="line">|     └── generated-sources/</span><br><span class="line">|        └── annotations/</span><br><span class="line">├── api-db-impl-redis/</span><br><span class="line">|  ├── api-db-impl-redis.iml</span><br><span class="line">|  ├── pom.xml</span><br><span class="line">|  ├── src/</span><br><span class="line">|  |  ├── main/</span><br><span class="line">|  |  |  ├── java/</span><br><span class="line">|  |  |  |  └── com/</span><br><span class="line">|  |  |  |     └── imlql/</span><br><span class="line">|  |  |  |        └── redis/</span><br><span class="line">|  |  |  |           └── RedisSaveService.java</span><br><span class="line">|  |  |  └── resources/</span><br><span class="line">|  |  |     └── META-INF/</span><br><span class="line">|  |  |        └── services/</span><br><span class="line">|  |  |           └── cn.imlql.data.DataSaveService</span><br><span class="line">|  |  └── test/</span><br><span class="line">|  |     └── java/</span><br><span class="line">|  └── target/</span><br><span class="line">|     ├── classes/</span><br><span class="line">|     |  ├── com/</span><br><span class="line">|     |  |  └── imlql/</span><br><span class="line">|     |  |     └── redis/</span><br><span class="line">|     |  |        └── RedisSaveService.class</span><br><span class="line">|     |  └── META-INF/</span><br><span class="line">|     |     └── services/</span><br><span class="line">|     |        └── cn.imlql.data.DataSaveService</span><br><span class="line">|     └── generated-sources/</span><br><span class="line">|        └── annotations/</span><br><span class="line">├── api-db-<span class="class"><span class="keyword">interface</span>/</span></span><br><span class="line"><span class="class">|  ├── <span class="title">api</span>-<span class="title">db</span>-<span class="title">interface</span>.<span class="title">iml</span></span></span><br><span class="line"><span class="class">|  ├── <span class="title">pom</span>.<span class="title">xml</span></span></span><br><span class="line"><span class="class">|  ├── <span class="title">src</span>/</span></span><br><span class="line"><span class="class">|  |  ├── <span class="title">main</span>/</span></span><br><span class="line"><span class="class">|  |  |  ├── <span class="title">java</span>/</span></span><br><span class="line"><span class="class">|  |  |  |  └── <span class="title">com</span>/</span></span><br><span class="line"><span class="class">|  |  |  |     └── <span class="title">imlql</span>/</span></span><br><span class="line"><span class="class">|  |  |  |        └── <span class="title">data</span>/</span></span><br><span class="line"><span class="class">|  |  |  |           └── <span class="title">DataSaveService</span>.<span class="title">java</span></span></span><br><span class="line"><span class="class">|  |  |  └── <span class="title">resources</span>/</span></span><br><span class="line"><span class="class">|  |  └── <span class="title">test</span>/</span></span><br><span class="line"><span class="class">|  |     └── <span class="title">java</span>/</span></span><br><span class="line"><span class="class">|  └── <span class="title">target</span>/</span></span><br><span class="line"><span class="class">|     ├── <span class="title">classes</span>/</span></span><br><span class="line"><span class="class">|     |  └── <span class="title">com</span>/</span></span><br><span class="line"><span class="class">|     |     └── <span class="title">imlql</span>/</span></span><br><span class="line"><span class="class">|     |        └── <span class="title">data</span>/</span></span><br><span class="line"><span class="class">|     |           └── <span class="title">DataSaveService</span>.<span class="title">class</span></span></span><br><span class="line"><span class="class">|     └── <span class="title">generated</span>-<span class="title">sources</span>/</span></span><br><span class="line"><span class="class">|        └── <span class="title">annotations</span>/</span></span><br><span class="line"><span class="class">├── <span class="title">app</span>/</span></span><br><span class="line"><span class="class">|  ├── <span class="title">app</span>.<span class="title">iml</span></span></span><br><span class="line"><span class="class">|  ├── <span class="title">pom</span>.<span class="title">xml</span></span></span><br><span class="line"><span class="class">|  ├── <span class="title">src</span>/</span></span><br><span class="line"><span class="class">|  |  ├── <span class="title">main</span>/</span></span><br><span class="line"><span class="class">|  |  |  ├── <span class="title">java</span>/</span></span><br><span class="line"><span class="class">|  |  |  |  └── <span class="title">com</span>/</span></span><br><span class="line"><span class="class">|  |  |  |     └── <span class="title">imlql</span>/</span></span><br><span class="line"><span class="class">|  |  |  |        └── <span class="title">redis</span>/</span></span><br><span class="line"><span class="class">|  |  |  |           └── <span class="title">MainTest</span>.<span class="title">java</span></span></span><br><span class="line"><span class="class">|  |  |  └── <span class="title">resources</span>/</span></span><br><span class="line"><span class="class">|  |  └── <span class="title">test</span>/</span></span><br><span class="line"><span class="class">|  |     └── <span class="title">java</span>/</span></span><br><span class="line"><span class="class">|  └── <span class="title">target</span>/</span></span><br><span class="line"><span class="class">|     ├── <span class="title">classes</span>/</span></span><br><span class="line"><span class="class">|     |  └── <span class="title">com</span>/</span></span><br><span class="line"><span class="class">|     |     └── <span class="title">imlql</span>/</span></span><br><span class="line"><span class="class">|     |        └── <span class="title">redis</span>/</span></span><br><span class="line"><span class="class">|     |           └── <span class="title">MainTest</span>.<span class="title">class</span></span></span><br><span class="line"><span class="class">|     └── <span class="title">generated</span>-<span class="title">sources</span>/</span></span><br><span class="line"><span class="class">|        └── <span class="title">annotations</span>/</span></span><br><span class="line"><span class="class">├── <span class="title">pom</span>.<span class="title">xml</span></span></span><br><span class="line"><span class="class">└── <span class="title">spi</span>-<span class="title">demo</span>.<span class="title">iml</span></span></span><br><span class="line"><span class="class"></span></span><br></pre></td></tr></table></figure>

<h3 id="MainTest"><a href="#MainTest" class="headerlink" title="MainTest"></a>MainTest</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> cn.imlql.data.DataSaveService;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.ServiceLoader;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 1、 ServiceLoader：load（）指定一个接口，</span></span><br><span class="line"><span class="comment"> *      他就会加载当前系统里面所有的这个接口的【指定实现】</span></span><br><span class="line"><span class="comment"> * 2、SPI（Service Provider Interface）</span></span><br><span class="line"><span class="comment"> *      接口工程---提供接口</span></span><br><span class="line"><span class="comment"> *          ---- 实现工程1  ： 实现接口 【META-INF/services 创建文件  接口名作为文件名  实现类全路径作为文件内容】</span></span><br><span class="line"><span class="comment"> *          ---- 实现工程2  ： 实现接口</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *      客户端----引用 工程1、或者 工程2</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//1、加载 可用的接口实现</span></span><br><span class="line">        ServiceLoader&lt;DataSaveService&gt; load = ServiceLoader.load(DataSaveService.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//拿到实现进行调用</span></span><br><span class="line">        <span class="keyword">for</span> (DataSaveService service : load) &#123;</span><br><span class="line">            service.saveData(<span class="string">&quot;你好....&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">MySQL保存了数据.......你好....</span><br><span class="line">Redis保存了数据.......你好....</span><br></pre></td></tr></table></figure>

<p>Java的SPI机制会默认加载<strong>类路径</strong>下<code>META-INF/services</code>的东西</p>
<h3 id="DataSaveService"><a href="#DataSaveService" class="headerlink" title="DataSaveService"></a>DataSaveService</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public interface DataSaveService &#123;</span><br><span class="line"></span><br><span class="line">    void saveData(String data);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="MySQLSaveService"><a href="#MySQLSaveService" class="headerlink" title="MySQLSaveService"></a>MySQLSaveService</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MySQLSaveService</span> <span class="keyword">implements</span> <span class="title">DataSaveService</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">saveData</span><span class="params">(String data)</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;MySQL保存了数据.......&quot;</span> + data);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="RedisSaveService"><a href="#RedisSaveService" class="headerlink" title="RedisSaveService"></a>RedisSaveService</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisSaveService</span> <span class="keyword">implements</span> <span class="title">DataSaveService</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">saveData</span><span class="params">(String data)</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Redis保存了数据.......&quot;</span>+data);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="SPI文件示例"><a href="#SPI文件示例" class="headerlink" title="SPI文件示例"></a>SPI文件示例</h3><p>api-db-impl-redis\src\main\resources\META-INF\services\cn.imlql.data.DataSaveService</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">cn.imlql.redis.RedisSaveService</span><br></pre></td></tr></table></figure>



<p>api-db-impl-mysql\src\main\resources\META-INF\services\cn.imlql.data.DataSaveService</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">cn.imlql.mysql.MySQLSaveService</span><br></pre></td></tr></table></figure>

<p>你没看错就是这么简单</p>
<h3 id="Java的SPI机制的作用"><a href="#Java的SPI机制的作用" class="headerlink" title="Java的SPI机制的作用"></a>Java的SPI机制的作用</h3><p>我只需要规定接口就可以开放给任何人实现</p>
<blockquote>
<p>META-INF\services下的文件，本文统称为<strong>SPI文件</strong></p>
</blockquote>
<h2 id="基于SPI启动Web容器"><a href="#基于SPI启动Web容器" class="headerlink" title="基于SPI启动Web容器"></a>基于SPI启动Web容器</h2><p>首先我们看到AppStarter实现了WebApplicationInitializer接口，官方文档是这样写的，那么WebApplicationInitializer肯定是能启动Web的核心</p>
<h3 id="WebApplicationInitializer"><a href="#WebApplicationInitializer" class="headerlink" title="WebApplicationInitializer"></a>WebApplicationInitializer</h3><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013190116630.png"  />



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@HandlesTypes(WebApplicationInitializer.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringServletContainerInitializer</span> <span class="keyword">implements</span> <span class="title">ServletContainerInitializer</span> </span>&#123;...&#125;</span><br></pre></td></tr></table></figure>

<h3 id="ServletContainerInitializer-onStartup"><a href="#ServletContainerInitializer-onStartup" class="headerlink" title="ServletContainerInitializer#onStartup()"></a>ServletContainerInitializer#onStartup()</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ServletContainerInitializer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStartup</span><span class="params">(Set&lt;Class&lt;?&gt;&gt; c, ServletContext ctx)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> ServletException</span>; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013190340419.png"/>





<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013192644360.png"/>



<p>相当于tomcat一启动会加载SpringServletContainerInitializer</p>
<h3 id="SpringServletContainerInitializer-onStartup"><a href="#SpringServletContainerInitializer-onStartup" class="headerlink" title="SpringServletContainerInitializer#onStartup()"></a>SpringServletContainerInitializer#onStartup()</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Servlet 3.0 &#123;<span class="doctag">@link</span> ServletContainerInitializer&#125; designed to support code-based</span></span><br><span class="line"><span class="comment"> * configuration of the servlet container using Spring&#x27;s &#123;<span class="doctag">@link</span> WebApplicationInitializer&#125;</span></span><br><span class="line"><span class="comment"> * SPI as opposed to (or possibly in combination with) the traditional</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> web.xml&#125;-based approach.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;h2&gt;See Also&lt;/h2&gt;</span></span><br><span class="line"><span class="comment"> * See &#123;<span class="doctag">@link</span> WebApplicationInitializer&#125; Javadoc for examples and detailed usage</span></span><br><span class="line"><span class="comment"> * recommendations.&lt;p&gt;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Chris Beams</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Juergen Hoeller</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Rossen Stoyanchev</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 3.1</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> #onStartup(Set, ServletContext)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> WebApplicationInitializer</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@HandlesTypes(WebApplicationInitializer.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringServletContainerInitializer</span> <span class="keyword">implements</span> <span class="title">ServletContainerInitializer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Delegate the &#123;<span class="doctag">@code</span> ServletContext&#125; to any &#123;<span class="doctag">@link</span> WebApplicationInitializer&#125;</span></span><br><span class="line"><span class="comment">    * implementations present on the application classpath.</span></span><br><span class="line"><span class="comment">    * &lt;p&gt;Because this class declares @&#123;<span class="doctag">@code</span> HandlesTypes(WebApplicationInitializer.class)&#125;,</span></span><br><span class="line"><span class="comment">    * Servlet 3.0+ containers will automatically scan the classpath for implementations</span></span><br><span class="line"><span class="comment">    * of Spring&#x27;s &#123;<span class="doctag">@code</span> WebApplicationInitializer&#125; interface and provide the set of all</span></span><br><span class="line"><span class="comment">    * such types to the &#123;<span class="doctag">@code</span> webAppInitializerClasses&#125; parameter of this method.</span></span><br><span class="line"><span class="comment">    * &lt;p&gt;If no &#123;<span class="doctag">@code</span> WebApplicationInitializer&#125; implementations are found on the classpath,</span></span><br><span class="line"><span class="comment">    * this method is effectively a no-op. An INFO-level log message will be issued notifying</span></span><br><span class="line"><span class="comment">    * the user that the &#123;<span class="doctag">@code</span> ServletContainerInitializer&#125; has indeed been invoked but that</span></span><br><span class="line"><span class="comment">    * no &#123;<span class="doctag">@code</span> WebApplicationInitializer&#125; implementations were found.</span></span><br><span class="line"><span class="comment">    * &lt;p&gt;Assuming that one or more &#123;<span class="doctag">@code</span> WebApplicationInitializer&#125; types are detected,</span></span><br><span class="line"><span class="comment">    * they will be instantiated (and &lt;em&gt;sorted&lt;/em&gt; if the @&#123;<span class="doctag">@link</span></span></span><br><span class="line"><span class="comment">    * org.springframework.core.annotation.Order <span class="doctag">@Order</span>&#125; annotation is present or</span></span><br><span class="line"><span class="comment">    * the &#123;<span class="doctag">@link</span> org.springframework.core.Ordered Ordered&#125; interface has been</span></span><br><span class="line"><span class="comment">    * implemented). Then the &#123;<span class="doctag">@link</span> WebApplicationInitializer#onStartup(ServletContext)&#125;</span></span><br><span class="line"><span class="comment">    * method will be invoked on each instance, delegating the &#123;<span class="doctag">@code</span> ServletContext&#125; such</span></span><br><span class="line"><span class="comment">    * that each instance may register and configure servlets such as Spring&#x27;s</span></span><br><span class="line"><span class="comment">    * &#123;<span class="doctag">@code</span> DispatcherServlet&#125;, listeners such as Spring&#x27;s &#123;<span class="doctag">@code</span> ContextLoaderListener&#125;,</span></span><br><span class="line"><span class="comment">    * or any other Servlet API componentry such as filters.</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> webAppInitializerClasses all implementations of</span></span><br><span class="line"><span class="comment">    * &#123;<span class="doctag">@link</span> WebApplicationInitializer&#125; found on the application classpath</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> servletContext the servlet context to be initialized</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@see</span> WebApplicationInitializer#onStartup(ServletContext)</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@see</span> AnnotationAwareOrderComparator</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">  	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStartup</span><span class="params">(<span class="meta">@Nullable</span> Set&lt;Class&lt;?&gt;&gt; webAppInitializerClasses, ServletContext servletContext)</span></span></span><br><span class="line"><span class="function">			<span class="keyword">throws</span> ServletException </span>&#123;</span><br><span class="line"></span><br><span class="line">		List&lt;WebApplicationInitializer&gt; initializers = Collections.emptyList();</span><br><span class="line"></span><br><span class="line">		<span class="keyword">if</span> (webAppInitializerClasses != <span class="keyword">null</span>) &#123;</span><br><span class="line">			initializers = <span class="keyword">new</span> ArrayList&lt;&gt;(webAppInitializerClasses.size());</span><br><span class="line">			<span class="keyword">for</span> (Class&lt;?&gt; waiClass : webAppInitializerClasses) &#123;</span><br><span class="line">				<span class="comment">// Be defensive: Some servlet containers provide us with invalid classes,</span></span><br><span class="line">				<span class="comment">// no matter what @HandlesTypes says... 所有的非接口非抽象的WebApplicationInitializer实现类</span></span><br><span class="line">				<span class="keyword">if</span> (!waiClass.isInterface() &amp;&amp; !Modifier.isAbstract(waiClass.getModifiers()) &amp;&amp;</span><br><span class="line">						WebApplicationInitializer.class.isAssignableFrom(waiClass)) &#123;</span><br><span class="line">					<span class="keyword">try</span> &#123;</span><br><span class="line">						initializers.add((WebApplicationInitializer) <span class="comment">//集合负责保存满足上面条件的类</span></span><br><span class="line">								ReflectionUtils.accessibleConstructor(waiClass).newInstance());</span><br><span class="line">					&#125;</span><br><span class="line">					<span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">						<span class="keyword">throw</span> <span class="keyword">new</span> ServletException(<span class="string">&quot;Failed to instantiate WebApplicationInitializer class&quot;</span>, ex);</span><br><span class="line">					&#125;</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">if</span> (initializers.isEmpty()) &#123;</span><br><span class="line">			servletContext.log(<span class="string">&quot;No Spring WebApplicationInitializer types detected on classpath&quot;</span>);</span><br><span class="line">			<span class="keyword">return</span>;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="comment">//下面会遍历所有满足要求的WebApplicationInitializer，调用他们的onStartup</span></span><br><span class="line">		servletContext.log(initializers.size() + <span class="string">&quot; Spring WebApplicationInitializers detected on classpath&quot;</span>);</span><br><span class="line">		AnnotationAwareOrderComparator.sort(initializers);</span><br><span class="line">		<span class="keyword">for</span> (WebApplicationInitializer initializer : initializers) &#123;</span><br><span class="line">			initializer.onStartup(servletContext); <span class="comment">//所有的 WebApplicationInitializer 的 onStartup</span></span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="HandlesTypes"><a href="#HandlesTypes" class="headerlink" title="@HandlesTypes"></a>@HandlesTypes</h3><ol>
<li>其中@HandlesTypes注解表示可以处理的类，在<code>onStartup</code> 方法中，可以通过<code>Set&lt;Class&lt;?&gt;&gt; webAppInitializerClasses</code> 获取得到。</li>
<li>@HandlesTypes属于sun公司对Servlet定义的规范，包括tomcat,jetty等服务器都对它有不同的实现</li>
<li>tomcat对于@HandlesTypes的具体实现咱们这里不深究，可以肯定的是一定用到了Java的SPI，如下。</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ServiceLoader&lt;DataSaveService&gt; load = ServiceLoader.load(WebApplicationInitializer.class);</span><br></pre></td></tr></table></figure>

<ol start="4">
<li>tomcat具体对于@HandlesTypes一定是和上面类似甚至是一样的代码来加载WebApplicationInitializer的实现</li>
</ol>
<p>咱们给它的方法onStartup打上断点</p>
<p>下面就是所有实现了WebApplicationInitializer的类</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013193308488.png" />

<p>接着在最底下的for循环执行所有实现了WebApplicationInitializer的类的onStartup(),然后就走到了我们的AppStarter</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013194526880.png"  />

<p>到这一步，ioc容器都没有创建，我们给refresh()打个断点，看什么时候启动的ioc</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013194649936.png"  />



<p>放行</p>
<p>我看的时候debug断点没有看到从AppStarter的哪一步跳到refresh()的。然后我一步一步走的时候发现不是在这个方法里调用的，注意看下面的图，DispatcherServlet已经new完了，但是debug依然没有跳到refresh()，说明不是在new  DispatcherServlet()的时候创建的容器</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211016204522610.png" />

<p>不过我凭经验猜测Springmvc里最重要的是DispatcherServlet，会不会是DispatcherServlet的那一步启动了IOC，我们开始进行下面的尝试</p>
<h3 id="Servlet相关"><a href="#Servlet相关" class="headerlink" title="Servlet相关"></a>Servlet相关</h3><ol>
<li><p>tomcat会遵循sun公司的规范给每一个Servlet创建对象</p>
</li>
<li><p>所以DispatcherServlet肯定也会创建对象</p>
</li>
<li><p>Servlet的规范</p>
<ol>
<li>Servlet创建对象</li>
<li>Servlet调用Init初始化</li>
<li>每次请求调用service处理</li>
<li>tomcat停止的时候调用destroy进行销毁</li>
</ol>
</li>
</ol>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013195139924.png"  />

<ol start="4">
<li>Serlvet是被谁调用开始初始化的属于tomcat的源码，我们这里不研究，我们这里只需要知道，每一个Servlet都会被初始化就可以了。</li>
</ol>
<h3 id="DispatcherServlet"><a href="#DispatcherServlet" class="headerlink" title="DispatcherServlet"></a>DispatcherServlet</h3><p>spring-web中有一个叫DispatcherServlet的类，很明显他是一个Servlet，所以tomcat启动的时候就会加载它，加载它的话当然是从父类一层一层加载的</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211013195948793.png" />

<ol>
<li>想要启动IOC容器，只可能是创建DispatcherServlet对象或者调用init()的时候来搞。上面我们也看到了，创建DispatcherServlet对象的时候debug调用栈并没有显示跳到了refresh方法，所以显然不是创建对象的时候</li>
<li>那就只有可能是调用init()的时候开始启动的IOC容器</li>
</ol>
<p>Servlet规范的init我看了下都是空的，从Spring的HttpServletBean才开始有东西，HttpServletBean的父类和接口对于init()都是空实现，下面我们就从HttpServletBean开始分析。</p>
<h3 id="HttpServletBean-init"><a href="#HttpServletBean-init" class="headerlink" title="HttpServletBean#init()"></a>HttpServletBean#init()</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> <span class="keyword">throws</span> ServletException </span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// Set bean properties from init parameters.</span></span><br><span class="line">   PropertyValues pvs = <span class="keyword">new</span> ServletConfigPropertyValues(getServletConfig(), <span class="keyword">this</span>.requiredProperties);</span><br><span class="line">   <span class="keyword">if</span> (!pvs.isEmpty()) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(<span class="keyword">this</span>);</span><br><span class="line">         ResourceLoader resourceLoader = <span class="keyword">new</span> ServletContextResourceLoader(getServletContext());</span><br><span class="line">         bw.registerCustomEditor(Resource.class, <span class="keyword">new</span> ResourceEditor(resourceLoader, getEnvironment()));</span><br><span class="line">         initBeanWrapper(bw);</span><br><span class="line">         bw.setPropertyValues(pvs, <span class="keyword">true</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">catch</span> (BeansException ex) &#123;</span><br><span class="line">         <span class="keyword">if</span> (logger.isErrorEnabled()) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;Failed to set bean properties on servlet &#x27;&quot;</span> + getServletName() + <span class="string">&quot;&#x27;&quot;</span>, ex);</span><br><span class="line">         &#125;</span><br><span class="line">         <span class="keyword">throw</span> ex;</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">//模板方法模式。给子类留的喜欢干的事 Let subclasses do whatever initialization they like.</span></span><br><span class="line">   initServletBean();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="FrameworkServlet-initServletBean"><a href="#FrameworkServlet-initServletBean" class="headerlink" title="FrameworkServlet#initServletBean()"></a>FrameworkServlet#initServletBean()</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/** 追踪看web应用启动做了什么。</span></span><br><span class="line"><span class="comment"> * Overridden method of &#123;<span class="doctag">@link</span> HttpServletBean&#125;, invoked after any bean properties</span></span><br><span class="line"><span class="comment"> * have been set. Creates this servlet&#x27;s WebApplicationContext.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">initServletBean</span><span class="params">()</span> <span class="keyword">throws</span> ServletException </span>&#123;</span><br><span class="line">   getServletContext().log(<span class="string">&quot;Initializing Spring &quot;</span> + getClass().getSimpleName() + <span class="string">&quot; &#x27;&quot;</span> + getServletName() + <span class="string">&quot;&#x27;&quot;</span>);</span><br><span class="line">   <span class="keyword">if</span> (logger.isInfoEnabled()) &#123;</span><br><span class="line">      logger.info(<span class="string">&quot;Initializing Servlet &#x27;&quot;</span> + getServletName() + <span class="string">&quot;&#x27;&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">   <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">this</span>.webApplicationContext = initWebApplicationContext(); <span class="comment">//初始化WebIOC容器，那我们想一下大概率是在这里启动的IOC容器</span></span><br><span class="line">      initFrameworkServlet(); <span class="comment">//这又是留给子类的</span></span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">catch</span> (ServletException | RuntimeException ex) &#123;</span><br><span class="line">      logger.error(<span class="string">&quot;Context initialization failed&quot;</span>, ex);</span><br><span class="line">      <span class="keyword">throw</span> ex;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</span><br><span class="line">      String value = <span class="keyword">this</span>.enableLoggingRequestDetails ?</span><br><span class="line">            <span class="string">&quot;shown which may lead to unsafe logging of potentially sensitive data&quot;</span> :</span><br><span class="line">            <span class="string">&quot;masked to prevent unsafe logging of potentially sensitive data&quot;</span>;</span><br><span class="line">      logger.debug(<span class="string">&quot;enableLoggingRequestDetails=&#x27;&quot;</span> + <span class="keyword">this</span>.enableLoggingRequestDetails +</span><br><span class="line">            <span class="string">&quot;&#x27;: request parameters and headers will be &quot;</span> + value);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (logger.isInfoEnabled()) &#123;</span><br><span class="line">      logger.info(<span class="string">&quot;Completed initialization in &quot;</span> + (System.currentTimeMillis() - startTime) + <span class="string">&quot; ms&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>DispatcherServlet没有重写initFrameworkServlet()</p>
<p>最后得到结论</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">this</span>.webApplicationContext = initWebApplicationContext(); <span class="comment">//初始化WebIOC容器，那我们想一下大概率是在这里启动的IOC容器</span></span><br></pre></td></tr></table></figure>

<p>下面开始具体分析</p>
<h2 id="小补充"><a href="#小补充" class="headerlink" title="小补充"></a>小补充</h2><h3 id="XML版如何配置父子容器关系？"><a href="#XML版如何配置父子容器关系？" class="headerlink" title="XML版如何配置父子容器关系？"></a>XML版如何配置父子容器关系？</h3><h4 id="web-xml"><a href="#web-xml" class="headerlink" title="web.xml"></a>web.xml</h4><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">web-app</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">listener</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">listener-class</span>&gt;</span>org.springframework.web.context.ContextLoaderListener<span class="tag">&lt;/<span class="name">listener-class</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">listener</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">context-param</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-name</span>&gt;</span>contextConfigLocation<span class="tag">&lt;/<span class="name">param-name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-value</span>&gt;</span>/WEB-INF/app-context.xml<span class="tag">&lt;/<span class="name">param-value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">context-param</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>app<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">servlet-class</span>&gt;</span>org.springframework.web.servlet.DispatcherServlet<span class="tag">&lt;/<span class="name">servlet-class</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">init-param</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">param-name</span>&gt;</span>contextConfigLocation<span class="tag">&lt;/<span class="name">param-name</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">param-value</span>&gt;</span><span class="tag">&lt;/<span class="name">param-value</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">init-param</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">load-on-startup</span>&gt;</span>1<span class="tag">&lt;/<span class="name">load-on-startup</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">servlet</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet-mapping</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>app<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>/app/*<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">servlet-mapping</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">web-app</span>&gt;</span></span><br></pre></td></tr></table></figure>



<ol>
<li>在web.xmI中配置C ontextl oaderListener,指定Spring配置文件的位置</li>
<li>在web.xml中配置 DispatcherServlet,指定SpringMVC配置文件位置</li>
<li>以上会产生父子容器</li>
</ol>
<p>父容器(Spring配置文件进行包扫描并保存所有组件的容器)</p>
<p>子容器(SpringMVC配置文件进行包扫描并保存所有组件的容器)</p>
<p>webloc.setParent(springloc)。类似于双亲委派，容器隔离。先看当前容器有没有这个组件，当前容器没有再去父容器找有没有这个组件</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/mvc-context-hierarchy.png"/>

<h3 id="AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC"><a href="#AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC" class="headerlink" title="AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC"></a>AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC</h3><p><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211016213745753.png" />AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC</p>
<blockquote>
<p>后面的讲解都用这个测试类</p>
</blockquote>
<h4 id="测试类-1"><a href="#测试类-1" class="headerlink" title="测试类"></a>测试类</h4><h5 id="QuickAppStarter"><a href="#QuickAppStarter" class="headerlink" title="QuickAppStarter"></a>QuickAppStarter</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 最快速的整合注解版SpringMVC和Spring的</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">QuickAppStarter</span> <span class="keyword">extends</span> <span class="title">AbstractAnnotationConfigDispatcherServletInitializer</span> </span>&#123;</span><br><span class="line">   <span class="meta">@Override</span> <span class="comment">//根容器的配置（Spring的配置文件===Spring的配置类）</span></span><br><span class="line">   <span class="keyword">protected</span> Class&lt;?&gt;[] getRootConfigClasses() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> Class&lt;?&gt;[]&#123;SpringConfig.class&#125;;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Override</span> <span class="comment">//web容器的配置（SpringMVC的配置文件===SpringMVC的配置类）</span></span><br><span class="line">   <span class="keyword">protected</span> Class&lt;?&gt;[] getServletConfigClasses() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> Class&lt;?&gt;[]&#123;SpringMVCConfig.class&#125;;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Override</span> <span class="comment">//Servlet的映射，DispatcherServlet的映射路径</span></span><br><span class="line">   <span class="keyword">protected</span> String[] getServletMappings() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> String[]&#123;<span class="string">&quot;/&quot;</span>&#125;;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Override</span></span><br><span class="line">   <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">customizeRegistration</span><span class="params">(ServletRegistration.Dynamic registration)</span> </span>&#123;</span><br><span class="line"><span class="comment">//    super.customizeRegistration(registration);</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//    registration.addMapping(&quot;&quot;);//</span></span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="SpringMVCConfig"><a href="#SpringMVCConfig" class="headerlink" title="SpringMVCConfig"></a>SpringMVCConfig</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * SpringMVC只扫描controller组件，可以不指定父容器类，让MVC扫所有。<span class="doctag">@Component</span>+<span class="doctag">@RequestMapping</span>就生效了</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ComponentScan(value = &quot;cn.imlql.web&quot;, includeFilters = &#123;</span></span><br><span class="line"><span class="meta">      @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)</span></span><br><span class="line"><span class="meta">&#125;, useDefaultFilters = false)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringMVCConfig</span> </span>&#123;</span><br><span class="line">   <span class="comment">//SpringMVC的子容器，能扫描的Spring容器中的组件</span></span><br><span class="line">   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="SpringConfig"><a href="#SpringConfig" class="headerlink" title="SpringConfig"></a>SpringConfig</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Spring不扫描controller组件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ComponentScan(value = &quot;cn.imlql.web&quot;,excludeFilters = &#123;</span></span><br><span class="line"><span class="meta">		@ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)</span></span><br><span class="line"><span class="meta">&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringConfig</span> </span>&#123;</span><br><span class="line">	<span class="comment">//Spring的父容器</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>父子容器隔离，因为有这句话webloc.setParent(springloc)，springmvc这个子容器可以扫描到父容器Spring里面的组件，反之Spring父容器扫描不到springmvc这个子容器的组件。</p>
<h5 id="HelloController-1"><a href="#HelloController-1" class="headerlink" title="HelloController"></a>HelloController</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//@Controller; 如果有父子容器 @Component，默认是在父容器中，还找不到</span></span><br><span class="line"><span class="comment">//@Component+@RequestMapping</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">HelloController</span><span class="params">()</span></span>&#123;</span><br><span class="line">      System.out.println(<span class="string">&quot;HelloController.....&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Autowired</span></span><br><span class="line">   HelloService helloService;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@GetMapping(&quot;/hello&quot;)</span> <span class="comment">// 所有的xxxMapping都是RequestMapping</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> String <span class="title">sayHello</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">      String mvc = helloService.say(<span class="string">&quot;MVC&quot;</span>);</span><br><span class="line">      <span class="keyword">return</span> mvc;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="HelloService"><a href="#HelloService" class="headerlink" title="HelloService"></a>HelloService</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">HelloService</span><span class="params">()</span></span>&#123;</span><br><span class="line">      System.out.println(<span class="string">&quot;HelloService.....&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> String <span class="title">say</span><span class="params">(String name)</span></span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;Hello,&quot;</span>+name;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ServletContextListener</span> <span class="keyword">extends</span> <span class="title">EventListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Receives notification that the web application initialization</span></span><br><span class="line"><span class="comment">     * process is starting.【翻译：在当前web应用启动以后（Tomcat把web应用加载了以后），调用contextInitialized方法】</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;p&gt;All ServletContextListeners are notified of context</span></span><br><span class="line"><span class="comment">     * initialization before any filters or servlets in the web</span></span><br><span class="line"><span class="comment">     * application are initialized.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> sce the ServletContextEvent containing the ServletContext</span></span><br><span class="line"><span class="comment">     * that is being initialized</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@implSpec</span></span></span><br><span class="line"><span class="comment">     * The default implementation takes no action.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title">contextInitialized</span><span class="params">(ServletContextEvent sce)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Receives notification that the ServletContext is about to be</span></span><br><span class="line"><span class="comment">     * shut down.</span></span><br><span class="line"><span class="comment">     *  </span></span><br><span class="line"><span class="comment">     * ......</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title">contextDestroyed</span><span class="params">(ServletContextEvent sce)</span> </span>&#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="SpringMVC父子容器的启动过程"><a href="#SpringMVC父子容器的启动过程" class="headerlink" title="SpringMVC父子容器的启动过程"></a>SpringMVC父子容器的启动过程</h2><h3 id="保存父子容器相关信息"><a href="#保存父子容器相关信息" class="headerlink" title="保存父子容器相关信息"></a>保存父子容器相关信息</h3><h4 id="SpringServletContainerInitializer-onStartup-1"><a href="#SpringServletContainerInitializer-onStartup-1" class="headerlink" title="SpringServletContainerInitializer#onStartup()"></a>SpringServletContainerInitializer#onStartup()</h4><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017171353930.png" />



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStartup</span><span class="params">(<span class="meta">@Nullable</span> Set&lt;Class&lt;?&gt;&gt; webAppInitializerClasses, ServletContext servletContext)</span></span></span><br><span class="line"><span class="function">         <span class="keyword">throws</span> ServletException </span>&#123;</span><br><span class="line"></span><br><span class="line">      List&lt;WebApplicationInitializer&gt; initializers = Collections.emptyList();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (webAppInitializerClasses != <span class="keyword">null</span>) &#123;</span><br><span class="line">         initializers = <span class="keyword">new</span> ArrayList&lt;&gt;(webAppInitializerClasses.size());</span><br><span class="line">         <span class="keyword">for</span> (Class&lt;?&gt; waiClass : webAppInitializerClasses) &#123;</span><br><span class="line">            <span class="comment">// Be defensive: Some servlet containers provide us with invalid classes,</span></span><br><span class="line">            <span class="comment">// no matter what @HandlesTypes says... 所有的非接口非抽象的WebApplicationInitializer实现类</span></span><br><span class="line">            <span class="keyword">if</span> (!waiClass.isInterface() &amp;&amp; !Modifier.isAbstract(waiClass.getModifiers()) &amp;&amp;</span><br><span class="line">                  WebApplicationInitializer.class.isAssignableFrom(waiClass)) &#123;</span><br><span class="line">               <span class="keyword">try</span> &#123;</span><br><span class="line">                  initializers.add((WebApplicationInitializer) <span class="comment">//集合负责保存满足上面条件的类</span></span><br><span class="line">                        ReflectionUtils.accessibleConstructor(waiClass).newInstance());</span><br><span class="line">               &#125;</span><br><span class="line">               <span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">                  <span class="keyword">throw</span> <span class="keyword">new</span> ServletException(<span class="string">&quot;Failed to instantiate WebApplicationInitializer class&quot;</span>, ex);</span><br><span class="line">               &#125;</span><br><span class="line">            &#125;</span><br><span class="line">         &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (initializers.isEmpty()) &#123;</span><br><span class="line">         servletContext.log(<span class="string">&quot;No Spring WebApplicationInitializer types detected on classpath&quot;</span>);</span><br><span class="line">         <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//下面会遍历所有满足要求的WebApplicationInitializer，调用他们的onStartup</span></span><br><span class="line">      servletContext.log(initializers.size() + <span class="string">&quot; Spring WebApplicationInitializers detected on classpath&quot;</span>);</span><br><span class="line">      AnnotationAwareOrderComparator.sort(initializers);</span><br><span class="line">      <span class="keyword">for</span> (WebApplicationInitializer initializer : initializers) &#123;</span><br><span class="line">         initializer.onStartup(servletContext); <span class="comment">//所有的 WebApplicationInitializer 的 onStartup</span></span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当initializer是咱们的QuickAppStarter时，F7进入方法。</p>
<h4 id="AbstractDispatcherServletInitializer-onStartup"><a href="#AbstractDispatcherServletInitializer-onStartup" class="headerlink" title="AbstractDispatcherServletInitializer#onStartup()"></a>AbstractDispatcherServletInitializer#onStartup()</h4><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017172450673.png" />

<p>因为咱们的QuickAppStarter没有onStarup()所以就调用了父类AbstractDispatcherServletInitializer的，没想到AbstractDispatcherServletInitializer也是继续调用父类的</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStartup</span><span class="params">(ServletContext servletContext)</span> <span class="keyword">throws</span> ServletException </span>&#123;</span><br><span class="line">   <span class="keyword">super</span>.onStartup(servletContext);</span><br><span class="line">   registerDispatcherServlet(servletContext);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="AbstractContextLoaderInitializer-onStartup"><a href="#AbstractContextLoaderInitializer-onStartup" class="headerlink" title="AbstractContextLoaderInitializer#onStartup()"></a>AbstractContextLoaderInitializer#onStartup()</h4><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017172942469.png" />



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractContextLoaderInitializer</span> <span class="keyword">implements</span> <span class="title">WebApplicationInitializer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/** Logger available to subclasses. */</span></span><br><span class="line">   <span class="keyword">protected</span> <span class="keyword">final</span> Log logger = LogFactory.getLog(getClass());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   <span class="meta">@Override</span> <span class="comment">//注册ContextLoaderListener；contextInitialized</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStartup</span><span class="params">(ServletContext servletContext)</span> <span class="keyword">throws</span> ServletException </span>&#123;</span><br><span class="line">      registerContextLoaderListener(servletContext);</span><br><span class="line">   &#125;</span><br><span class="line">    </span><br><span class="line">   <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">registerContextLoaderListener</span><span class="params">(ServletContext servletContext)</span> </span>&#123;</span><br><span class="line">		WebApplicationContext rootAppContext = createRootApplicationContext(); <span class="comment">//创建一个根容器</span></span><br><span class="line">		<span class="keyword">if</span> (rootAppContext != <span class="keyword">null</span>) &#123;</span><br><span class="line">			ContextLoaderListener listener = <span class="keyword">new</span> ContextLoaderListener(rootAppContext);</span><br><span class="line">			listener.setContextInitializers(getRootApplicationContextInitializers());</span><br><span class="line">			servletContext.addListener(listener);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> &#123;</span><br><span class="line">			logger.debug(<span class="string">&quot;No ContextLoaderListener registered, as &quot;</span> +</span><br><span class="line">					<span class="string">&quot;createRootApplicationContext() did not return an application context&quot;</span>);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211016214957367.png"/>





<h4 id="AbstractAnnotationConfigDispatcherServletInitializer-createRootApplicationContext-创建Spring-IOC父容器"><a href="#AbstractAnnotationConfigDispatcherServletInitializer-createRootApplicationContext-创建Spring-IOC父容器" class="headerlink" title="AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext()创建Spring-IOC父容器"></a>AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext()创建Spring-IOC父容器</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Nullable</span> <span class="comment">//重写了爷爷类的创建根容器方法</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> WebApplicationContext <span class="title">createRootApplicationContext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">   Class&lt;?&gt;[] configClasses = getRootConfigClasses(); <span class="comment">//获取根配置</span></span><br><span class="line">   <span class="keyword">if</span> (!ObjectUtils.isEmpty(configClasses)) &#123;</span><br><span class="line">      AnnotationConfigWebApplicationContext context = <span class="keyword">new</span> AnnotationConfigWebApplicationContext();</span><br><span class="line">      context.register(configClasses); <span class="comment">//创建了一个IOC容器并把配置类注册进来</span></span><br><span class="line">      <span class="keyword">return</span> context;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017174011291.png" />

<p>getRootConfigClasses()正好是咱们QuickAppStarter这个子类重写的，debug F7进入</p>
<h4 id="QuickAppStarter-getRootConfigClasses-拿自定义的配置类"><a href="#QuickAppStarter-getRootConfigClasses-拿自定义的配置类" class="headerlink" title="QuickAppStarter#getRootConfigClasses()拿自定义的配置类"></a>QuickAppStarter#getRootConfigClasses()拿自定义的配置类</h4><p>果不其然，调用了QuickAppStarter#getRootConfigClasses()</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017174118178.png"/>



<p>继续往下走创建Web容器，这是Spring父容器，因为你看它getRootConfigClasses()获取的是父容器配置</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017174500213.png"/>



<p>然后返回</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017174828598.png" />



<p>图片上面写错了。应该改为：<strong>保存了上面创建的Spring-IOC父容器</strong>。Web子容器的创建在下面</p>
<h4 id="ContextLoaderListener创建一个监听器准备后续启动容器进行回调"><a href="#ContextLoaderListener创建一个监听器准备后续启动容器进行回调" class="headerlink" title="ContextLoaderListener创建一个监听器准备后续启动容器进行回调"></a>ContextLoaderListener创建一个监听器准备后续启动容器进行回调</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ContextLoaderListener</span> <span class="keyword">extends</span> <span class="title">ContextLoader</span> <span class="keyword">implements</span> <span class="title">ServletContextListener</span> </span>&#123;</span><br><span class="line">   <span class="comment">//在当前web应用启动以后（Tomcat把web应用加载了以后），调用contextInitialized方法</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">ContextLoaderListener</span><span class="params">(WebApplicationContext context)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">super</span>(context);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Initialize the root web application context.</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="meta">@Override</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">contextInitialized</span><span class="params">(ServletContextEvent event)</span> </span>&#123; <span class="comment">//根容器初始化</span></span><br><span class="line">      initWebApplicationContext(event.getServletContext()); <span class="comment">//初始化webioc容器</span></span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面是利用Servlet标准</p>
<h4 id="返回到AbstractDispatcherServletInitializer-onStartup"><a href="#返回到AbstractDispatcherServletInitializer-onStartup" class="headerlink" title="返回到AbstractDispatcherServletInitializer#onStartup()"></a>返回到AbstractDispatcherServletInitializer#onStartup()</h4><p>接着就继续返回</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017175108566.png"/>



<p>下面才是创建Web子容器（也叫做Servlet容器）</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">registerDispatcherServlet</span><span class="params">(ServletContext servletContext)</span> </span>&#123;</span><br><span class="line">   String servletName = getServletName();</span><br><span class="line">   Assert.hasLength(servletName, <span class="string">&quot;getServletName() must not return null or empty&quot;</span>);</span><br><span class="line"></span><br><span class="line">   WebApplicationContext servletAppContext = createServletApplicationContext(); <span class="comment">//创建Servlet容器</span></span><br><span class="line">   Assert.notNull(servletAppContext, <span class="string">&quot;createServletApplicationContext() must not return null&quot;</span>);</span><br><span class="line"></span><br><span class="line">   FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);</span><br><span class="line">   Assert.notNull(dispatcherServlet, <span class="string">&quot;createDispatcherServlet(WebApplicationContext) must not return null&quot;</span>);</span><br><span class="line">   dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());</span><br><span class="line"></span><br><span class="line">   ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);</span><br><span class="line">   <span class="keyword">if</span> (registration == <span class="keyword">null</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;Failed to register servlet with name &#x27;&quot;</span> + servletName + <span class="string">&quot;&#x27;. &quot;</span> +</span><br><span class="line">            <span class="string">&quot;Check if there is another servlet registered under the same name.&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   registration.setLoadOnStartup(<span class="number">1</span>);</span><br><span class="line">   registration.addMapping(getServletMappings()); <span class="comment">//根据我们指定的DispatcherServlet的路径进行注册</span></span><br><span class="line">   registration.setAsyncSupported(isAsyncSupported());</span><br><span class="line"></span><br><span class="line">   Filter[] filters = getServletFilters();</span><br><span class="line">   <span class="keyword">if</span> (!ObjectUtils.isEmpty(filters)) &#123;</span><br><span class="line">      <span class="keyword">for</span> (Filter filter : filters) &#123;</span><br><span class="line">         registerServletFilter(servletContext, filter);</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line"><span class="comment">// 这个是留给我们自定义的方法，模板模式</span></span><br><span class="line">   customizeRegistration(registration);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017175449110.png" />



<h4 id="AbstractAnnotationConfigDispatcherServletInitializer-createServletApplicationContext-创建Web子容器-Servelt容器"><a href="#AbstractAnnotationConfigDispatcherServletInitializer-createServletApplicationContext-创建Web子容器-Servelt容器" class="headerlink" title="AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext()创建Web子容器(Servelt容器)"></a>AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext()创建Web子容器(Servelt容器)</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> WebApplicationContext <span class="title">createServletApplicationContext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//</span></span><br><span class="line">   AnnotationConfigWebApplicationContext context = <span class="keyword">new</span> AnnotationConfigWebApplicationContext();</span><br><span class="line">   Class&lt;?&gt;[] configClasses = getServletConfigClasses(); <span class="comment">//获取web应用的配置</span></span><br><span class="line">   <span class="keyword">if</span> (!ObjectUtils.isEmpty(configClasses)) &#123;</span><br><span class="line">      context.register(configClasses);</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> context;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017175724861.png"/>

<p>这里又new了一个容器，和上面那个容器一样都没有初始化。这里也是调用咱们QuickAppStarter重写的方法，因为这里调用的是getServletConfigClasses()，所以很明显这里的容器是Web子容器</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017180020098.png" />

<p>然后就一路往回返，走到这里</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017180356531.png"/>

<p>继续F7进入</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017180749457.png" />

<p>这里就是保存咱们上面刚创建的Web子容器，然后再返回</p>
<h4 id="返回到SpringServletContainerInitializer-onStartup"><a href="#返回到SpringServletContainerInitializer-onStartup" class="headerlink" title="返回到SpringServletContainerInitializer#onStartup()"></a>返回到SpringServletContainerInitializer#onStartup()</h4><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017181428636.png"/>

<ol>
<li>这里应用就加载完了，接下来干嘛呢？</li>
<li>你往前看看，咱们的Spring容器和Web子容器都是只是创建完了，都还没有初始化，甚至都没有webloc.setParent(springloc)这样产生父子容器的关系</li>
<li>回想下前面讲的，是不是有一个监听器，那你说把容器保存到监听器里，它能是干嘛的？猜想一下不就是应用加载完之后，发送一个什么事件，然后根据这个事件触发监听器初始化两大容器等等。</li>
</ol>
<p>咱们就把那个监听器的初始化方法打个断点，然后F8放行试试呗</p>
<h3 id="ContextLoaderListener回调-开始refresh刷新Spring的根容器-父容器"><a href="#ContextLoaderListener回调-开始refresh刷新Spring的根容器-父容器" class="headerlink" title="ContextLoaderListener回调-开始refresh刷新Spring的根容器(父容器)"></a>ContextLoaderListener回调-开始refresh刷新Spring的根容器(父容器)</h3><h4 id="ContextLoaderListener-contextInitialized"><a href="#ContextLoaderListener-contextInitialized" class="headerlink" title="ContextLoaderListener#contextInitialized()"></a>ContextLoaderListener#contextInitialized()</h4><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017182550786.png" />



<h4 id="简单看一下Tomcat里的调用源码"><a href="#简单看一下Tomcat里的调用源码" class="headerlink" title="简单看一下Tomcat里的调用源码"></a>简单看一下Tomcat里的调用源码</h4><p>然后真的走到了这里。tomcat里的代码位置是乱的，乱的意思就是比如说上面写的是4766行的调用，但实际上那里是个<code>&#125;</code>大括号。也不知道是什么问题，所以我们就大致看下tomcat的代码，不细究。</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017183133699.png" />

<p>这里的调用还是对的</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017183255872.png" />



<p>应该就是类似这样的调用</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017183351172.png" />



<p>走的应该是第一个if</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017183536609.png"/>



<p>大致就是tomcat里的一个监听器基于事件回调的原理，咱们就不深究了。</p>
<h4 id="ContextLoader-initWebApplicationContext"><a href="#ContextLoader-initWebApplicationContext" class="headerlink" title="ContextLoader#initWebApplicationContext()"></a>ContextLoader#initWebApplicationContext()</h4><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017183828726.png" />



<p>F7进入，这里因为我重新启动了一次，所以你看到根容器是@3661</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017184039846.png"  />

<p>终于要调用refresh了</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017184056048.png" />

<p>这里直接放行到容器refresh完毕看下父容器</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017184405806.png" />

<p>父容器只扫描了，springconfig和helloService，我们继续放行看下Web子容器.</p>
<h3 id="Servlet初始化-进而开始refresh刷新Spring的web子容器"><a href="#Servlet初始化-进而开始refresh刷新Spring的web子容器" class="headerlink" title="Servlet初始化-进而开始refresh刷新Spring的web子容器"></a>Servlet初始化-进而开始refresh刷新Spring的web子容器</h3><h4 id="HttpServletBean-init-1"><a href="#HttpServletBean-init-1" class="headerlink" title="HttpServletBean#init()"></a>HttpServletBean#init()</h4><p>跳到了这里，为什么会跳到这里呢？记不记得之前我们用DispatcherServlet保存了Web子容器，这里就要调用DispatcherServlet的相关初始化方法</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017185442060.png"/>



<h4 id="FrameworkServlet-initWebApplicationContext"><a href="#FrameworkServlet-initWebApplicationContext" class="headerlink" title="FrameworkServlet#initWebApplicationContext()"></a>FrameworkServlet#initWebApplicationContext()</h4><p>一路放行</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017191309358.png"/>

<p>上面父子容器关系形成了，并且父容器已经refresh完毕</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> WebApplicationContext <span class="title">initWebApplicationContext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">   WebApplicationContext rootContext =</span><br><span class="line">         WebApplicationContextUtils.getWebApplicationContext(getServletContext()); <span class="comment">//父容器</span></span><br><span class="line">   WebApplicationContext wac = <span class="keyword">null</span>; <span class="comment">//先会获取之前的 WebApplicationContext（构建父子容器）</span></span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (<span class="keyword">this</span>.webApplicationContext != <span class="keyword">null</span>) &#123;</span><br><span class="line">      <span class="comment">// A context instance was injected at construction time -&gt; use it</span></span><br><span class="line">      wac = <span class="keyword">this</span>.webApplicationContext; <span class="comment">//当前的web-ioc容器</span></span><br><span class="line">      <span class="keyword">if</span> (wac <span class="keyword">instanceof</span> ConfigurableWebApplicationContext) &#123;</span><br><span class="line">         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;</span><br><span class="line">         <span class="keyword">if</span> (!cwac.isActive()) &#123;</span><br><span class="line">            <span class="comment">// The context has not yet been refreshed -&gt; provide services such as</span></span><br><span class="line">            <span class="comment">// setting the parent context, setting the application context id, etc</span></span><br><span class="line">            <span class="keyword">if</span> (cwac.getParent() == <span class="keyword">null</span>) &#123;</span><br><span class="line">               <span class="comment">// The context instance was injected without an explicit parent -&gt; set</span></span><br><span class="line">               <span class="comment">// the root application context (if any; may be null) as the parent</span></span><br><span class="line">               cwac.setParent(rootContext); <span class="comment">//父子容器的体现，</span></span><br><span class="line">            &#125;</span><br><span class="line">            configureAndRefreshWebApplicationContext(cwac); <span class="comment">//配置并且刷新容器</span></span><br><span class="line">         &#125;</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">if</span> (wac == <span class="keyword">null</span>) &#123;</span><br><span class="line">      <span class="comment">// No context instance was injected at construction time -&gt; see if one</span></span><br><span class="line">      <span class="comment">// has been registered in the servlet context. If one exists, it is assumed</span></span><br><span class="line">      <span class="comment">// that the parent context (if any) has already been set and that the</span></span><br><span class="line">      <span class="comment">// user has performed any initialization such as setting the context id</span></span><br><span class="line">      wac = findWebApplicationContext();</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">if</span> (wac == <span class="keyword">null</span>) &#123;</span><br><span class="line">      <span class="comment">// No context instance is defined for this servlet -&gt; create a local one</span></span><br><span class="line">      wac = createWebApplicationContext(rootContext);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (!<span class="keyword">this</span>.refreshEventReceived) &#123;</span><br><span class="line">      <span class="comment">// Either the context is not a ConfigurableApplicationContext with refresh</span></span><br><span class="line">      <span class="comment">// support or the context injected at construction time had already been</span></span><br><span class="line">      <span class="comment">// refreshed -&gt; trigger initial onRefresh manually here.</span></span><br><span class="line">      <span class="keyword">synchronized</span> (<span class="keyword">this</span>.onRefreshMonitor) &#123;</span><br><span class="line">         onRefresh(wac);</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (<span class="keyword">this</span>.publishContext) &#123;</span><br><span class="line">      <span class="comment">// Publish the context as a servlet context attribute.</span></span><br><span class="line">      String attrName = getServletContextAttributeName();</span><br><span class="line">      getServletContext().setAttribute(attrName, wac);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">return</span> wac;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="FrameworkServlet-createWebApplicationContext"><a href="#FrameworkServlet-createWebApplicationContext" class="headerlink" title="FrameworkServlet#createWebApplicationContext()"></a>FrameworkServlet#createWebApplicationContext()</h4><img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017191503347.png"  />

<p>再次来到Web子容器的刷新</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.6/spring-sourcecode-v1/chapter_08/image-20211017191705766.png"  />

<ol>
<li>然后我们看到子容器只有它自己的东西</li>
<li>虽然子容器只有controller，但是因为它保存了父容器。所以它是可以拿到HelloService的，也就是我们可以在HelloController里装配HelloService</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Autowired</span></span><br><span class="line">	HelloService helloService;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol start="3">
<li>但是如果想在父容器里装配HelloController就会报错，父容器没有保存子容器。</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Autowired</span></span><br><span class="line">	HelloController helloController;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol start="4">
<li>这样就很好的实现容器隔离，避免Service乱引用Controller</li>
<li>实现父子容器隔离的前提就是前面写的SpringConfig不扫描controller，交给Web子容器</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Spring不扫描controller组件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ComponentScan(value = &quot;cn.imlql.web&quot;,excludeFilters = &#123;</span></span><br><span class="line"><span class="meta">		@ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)</span></span><br><span class="line"><span class="meta">&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringConfig</span> </span>&#123;</span><br><span class="line">	<span class="comment">//Spring的父容器</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * SpringMVC只扫描controller组件，可以不指定父容器类，让MVC扫所有。<span class="doctag">@Component</span>+<span class="doctag">@RequestMapping</span>就生效了</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ComponentScan(value = &quot;cn.imlql.web&quot;, includeFilters = &#123;</span></span><br><span class="line"><span class="meta">      @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)</span></span><br><span class="line"><span class="meta">&#125;, useDefaultFilters = false)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringMVCConfig</span> </span>&#123;</span><br><span class="line">   <span class="comment">//SpringMVC的子容器，能扫描的Spring容器中的组件</span></span><br><span class="line">   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意找组件的过程是先找自己的容器，自己没有再到父容器里找</p>
<blockquote>
<ol>
<li>总的来说Spring父容器和SpringMVC子容器启动过程是来自于tomcat的两个回调</li>
<li>Serlvet应用启动，监听器回调刷新Spring父容器</li>
<li>Servlet初始化，回调刷新SpringMVC子容器</li>
</ol>
</blockquote>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">youthlql</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://imlql.cn/post/c8dd1418.html">https://imlql.cn/post/c8dd1418.html</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://imlql.cn" target="_blank">风祈的时光录</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Spring%E6%BA%90%E7%A0%81/">Spring源码</a></div><div class="post_share"></div></div><div class="post-reward"><div class="reward-button"><i class="fas fa-qrcode"></i> 打赏</div><div class="reward-main"><ul class="reward-all"><li class="reward-item"><a href="https://npm.elemecdn.com/youthlql@1.0.8/blog/wechat.jpg" target="_blank"><img class="post-qr-code-img" src= "" data-lazy-src="https://npm.elemecdn.com/youthlql@1.0.8/blog/wechat.jpg" alt="微信"/></a><div class="post-qr-code-desc">微信</div></li><li class="reward-item"><a href="https://npm.elemecdn.com/youthlql@1.0.8/blog/alipay.jpg" target="_blank"><img class="post-qr-code-img" src= "" data-lazy-src="https://npm.elemecdn.com/youthlql@1.0.8/blog/alipay.jpg" alt="支付宝"/></a><div class="post-qr-code-desc">支付宝</div></li></ul></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/post/6f2cef28.html"><img class="prev-cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Spring源码系列-第9章-SpringMVC请求处理源码和HandlerMapping原理</div></div></a></div><div class="next-post pull-right"><a href="/post/fb3552e0.html"><img class="next-cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Spring源码系列-第7章-AOP的执行流程原理和监听器原理</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/post/41fb8d9e.html" title="Spring源码系列-第2章-后置工厂处理器和Bean生命周期"><img class="cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-12-21</div><div class="title">Spring源码系列-第2章-后置工厂处理器和Bean生命周期</div></div></a></div><div><a href="/post/49f419ae.html" title="Spring源码系列-第4章-Bean初始化流程"><img class="cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-01-27</div><div class="title">Spring源码系列-第4章-Bean初始化流程</div></div></a></div><div><a href="/post/fb3552e0.html" title="Spring源码系列-第7章-AOP的执行流程原理和监听器原理"><img class="cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-04-17</div><div class="title">Spring源码系列-第7章-AOP的执行流程原理和监听器原理</div></div></a></div><div><a href="/post/6f2cef28.html" title="Spring源码系列-第9章-SpringMVC请求处理源码和HandlerMapping原理"><img class="cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-06-21</div><div class="title">Spring源码系列-第9章-SpringMVC请求处理源码和HandlerMapping原理</div></div></a></div><div><a href="/post/4dfb096b.html" title="Spring源码系列-第11章-SpringMVC异常处理源码和@EnableWebMvc原理"><img class="cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-01-05</div><div class="title">Spring源码系列-第11章-SpringMVC异常处理源码和@EnableWebMvc原理</div></div></a></div><div><a href="/post/e3da4d66.html" title="Spring源码系列-第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理"><img class="cover" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/spring.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2023-01-06</div><div class="title">Spring源码系列-第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E7%AC%AC8%E7%AB%A0-SpringMVC%E5%AD%90%E5%AE%B9%E5%99%A8%E5%92%8CSpring%E7%88%B6%E5%AE%B9%E5%99%A8%E7%9A%84%E5%90%AF%E5%8A%A8%E5%8E%9F%E7%90%86"><span class="toc-text">第8章-SpringMVC子容器和Spring父容器的启动原理</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%B5%8B%E8%AF%95%E7%B1%BB"><span class="toc-text">测试类</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#AppStarter"><span class="toc-text">AppStarter</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#HelloController"><span class="toc-text">HelloController</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AppConfig"><span class="toc-text">AppConfig</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Java%E7%9A%84SPI%E6%9C%BA%E5%88%B6"><span class="toc-text">Java的SPI机制</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%A1%B9%E7%9B%AE%E7%9B%AE%E5%BD%95"><span class="toc-text">项目目录</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#MainTest"><span class="toc-text">MainTest</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DataSaveService"><span class="toc-text">DataSaveService</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#MySQLSaveService"><span class="toc-text">MySQLSaveService</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#RedisSaveService"><span class="toc-text">RedisSaveService</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SPI%E6%96%87%E4%BB%B6%E7%A4%BA%E4%BE%8B"><span class="toc-text">SPI文件示例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Java%E7%9A%84SPI%E6%9C%BA%E5%88%B6%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="toc-text">Java的SPI机制的作用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9F%BA%E4%BA%8ESPI%E5%90%AF%E5%8A%A8Web%E5%AE%B9%E5%99%A8"><span class="toc-text">基于SPI启动Web容器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#WebApplicationInitializer"><span class="toc-text">WebApplicationInitializer</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ServletContainerInitializer-onStartup"><span class="toc-text">ServletContainerInitializer#onStartup()</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#SpringServletContainerInitializer-onStartup"><span class="toc-text">SpringServletContainerInitializer#onStartup()</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#HandlesTypes"><span class="toc-text">@HandlesTypes</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Servlet%E7%9B%B8%E5%85%B3"><span class="toc-text">Servlet相关</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#DispatcherServlet"><span class="toc-text">DispatcherServlet</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#HttpServletBean-init"><span class="toc-text">HttpServletBean#init()</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#FrameworkServlet-initServletBean"><span class="toc-text">FrameworkServlet#initServletBean()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%B0%8F%E8%A1%A5%E5%85%85"><span class="toc-text">小补充</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#XML%E7%89%88%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E7%88%B6%E5%AD%90%E5%AE%B9%E5%99%A8%E5%85%B3%E7%B3%BB%EF%BC%9F"><span class="toc-text">XML版如何配置父子容器关系？</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#web-xml"><span class="toc-text">web.xml</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AbstractAnnotationConfigDispatcherServletInitializer%E8%83%BD%E6%9B%B4%E5%BF%AB%E7%9A%84%E6%95%B4%E5%90%88Spring%E5%92%8CSpringMVC"><span class="toc-text">AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%B5%8B%E8%AF%95%E7%B1%BB-1"><span class="toc-text">测试类</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#QuickAppStarter"><span class="toc-text">QuickAppStarter</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#SpringMVCConfig"><span class="toc-text">SpringMVCConfig</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#SpringConfig"><span class="toc-text">SpringConfig</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#HelloController-1"><span class="toc-text">HelloController</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#HelloService"><span class="toc-text">HelloService</span></a></li></ol></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#SpringMVC%E7%88%B6%E5%AD%90%E5%AE%B9%E5%99%A8%E7%9A%84%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B"><span class="toc-text">SpringMVC父子容器的启动过程</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BF%9D%E5%AD%98%E7%88%B6%E5%AD%90%E5%AE%B9%E5%99%A8%E7%9B%B8%E5%85%B3%E4%BF%A1%E6%81%AF"><span class="toc-text">保存父子容器相关信息</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#SpringServletContainerInitializer-onStartup-1"><span class="toc-text">SpringServletContainerInitializer#onStartup()</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AbstractDispatcherServletInitializer-onStartup"><span class="toc-text">AbstractDispatcherServletInitializer#onStartup()</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AbstractContextLoaderInitializer-onStartup"><span class="toc-text">AbstractContextLoaderInitializer#onStartup()</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AbstractAnnotationConfigDispatcherServletInitializer-createRootApplicationContext-%E5%88%9B%E5%BB%BASpring-IOC%E7%88%B6%E5%AE%B9%E5%99%A8"><span class="toc-text">AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext()创建Spring-IOC父容器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#QuickAppStarter-getRootConfigClasses-%E6%8B%BF%E8%87%AA%E5%AE%9A%E4%B9%89%E7%9A%84%E9%85%8D%E7%BD%AE%E7%B1%BB"><span class="toc-text">QuickAppStarter#getRootConfigClasses()拿自定义的配置类</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#ContextLoaderListener%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%9B%91%E5%90%AC%E5%99%A8%E5%87%86%E5%A4%87%E5%90%8E%E7%BB%AD%E5%90%AF%E5%8A%A8%E5%AE%B9%E5%99%A8%E8%BF%9B%E8%A1%8C%E5%9B%9E%E8%B0%83"><span class="toc-text">ContextLoaderListener创建一个监听器准备后续启动容器进行回调</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E5%88%B0AbstractDispatcherServletInitializer-onStartup"><span class="toc-text">返回到AbstractDispatcherServletInitializer#onStartup()</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AbstractAnnotationConfigDispatcherServletInitializer-createServletApplicationContext-%E5%88%9B%E5%BB%BAWeb%E5%AD%90%E5%AE%B9%E5%99%A8-Servelt%E5%AE%B9%E5%99%A8"><span class="toc-text">AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext()创建Web子容器(Servelt容器)</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BF%94%E5%9B%9E%E5%88%B0SpringServletContainerInitializer-onStartup"><span class="toc-text">返回到SpringServletContainerInitializer#onStartup()</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ContextLoaderListener%E5%9B%9E%E8%B0%83-%E5%BC%80%E5%A7%8Brefresh%E5%88%B7%E6%96%B0Spring%E7%9A%84%E6%A0%B9%E5%AE%B9%E5%99%A8-%E7%88%B6%E5%AE%B9%E5%99%A8"><span class="toc-text">ContextLoaderListener回调-开始refresh刷新Spring的根容器(父容器)</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#ContextLoaderListener-contextInitialized"><span class="toc-text">ContextLoaderListener#contextInitialized()</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%AE%80%E5%8D%95%E7%9C%8B%E4%B8%80%E4%B8%8BTomcat%E9%87%8C%E7%9A%84%E8%B0%83%E7%94%A8%E6%BA%90%E7%A0%81"><span class="toc-text">简单看一下Tomcat里的调用源码</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#ContextLoader-initWebApplicationContext"><span class="toc-text">ContextLoader#initWebApplicationContext()</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Servlet%E5%88%9D%E5%A7%8B%E5%8C%96-%E8%BF%9B%E8%80%8C%E5%BC%80%E5%A7%8Brefresh%E5%88%B7%E6%96%B0Spring%E7%9A%84web%E5%AD%90%E5%AE%B9%E5%99%A8"><span class="toc-text">Servlet初始化-进而开始refresh刷新Spring的web子容器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#HttpServletBean-init-1"><span class="toc-text">HttpServletBean#init()</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#FrameworkServlet-initWebApplicationContext"><span class="toc-text">FrameworkServlet#initWebApplicationContext()</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#FrameworkServlet-createWebApplicationContext"><span class="toc-text">FrameworkServlet#createWebApplicationContext()</span></a></li></ol></li></ol></li></ol></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By youthlql</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text"><a href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral" target="_blank" rel="noopener" class="one-pan-link-mark"><img style="position:relative;top:-3px; " src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/upyun/logo.png" align="absmiddle" width="60px" height="30px"></a><a target="_blank" rel="noopener" href="https://beian.miit.gov.cn"><img class="icp-icon" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/icp.png"><span>鄂ICP备19028890号</span></a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">简</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">本地搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://npm.elemecdn.com/@fancyapps/ui/dist/fancybox.umd.js"></script><script src="https://npm.elemecdn.com/instant.page/instantpage.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script defer src="https://npm.elemecdn.com/jquery@latest/dist/jquery.min.js"></script></div></body></html>